package connection;

import java.net.InetSocketAddress;
import java.security.cert.Certificate;

import messages.CcAuthNounceMessage;
import messages.CcPKExchangeMessage;
import messages.CcSKExchangeMessage;
import messages.CertChallengeMessage;
import messages.CertSKExchangeMessage;
import messages.SecChannelChallengeMessage;
import messages.UnexpectedMessage;
import messages.UserPassPKExchangeMessage;
import messages.UserPassSKExchangeMessage;

import org.bouncycastle.crypto.CryptoException;

import security.Hash;
import security.SecureRandomNumber;
import security.crypto.SimmEncryption;
import server.ServerData;
import connection.exception.ConnectionResetException;

public class TCPSecureServerSocket extends TCPSecureSocket {

	private ServerData _serverData;

	private boolean _vericop = false;

	public TCPSecureServerSocket(TCPSocket tcpSocket) throws ConnectionResetException{
		_endpointId = "CERTCOP";
		_tcpSocket = tcpSocket;	
		_serverData = ServerData.getServerData();

		_privateKey = _serverData.getKeystoreManager().getServerPrivateKey(true);

		_establishingSecureChannel = true;
		_secureChannelEstablished = false;
	}

	public void waitChannelEstablishmentMode() throws ConnectionResetException{
		Object obj = null;
		while(obj == null)
			obj = receive();

		if(obj.getClass().getSimpleName().equals("UserPassPKExchangeMessage"))
			_secureChannelEstablished = establishUserPassSecureChannel((UserPassPKExchangeMessage)obj);
		else if(obj.getClass().getSimpleName().equals("CcPKExchangeMessage"))
			_secureChannelEstablished = establishCcSecureChannel((CcPKExchangeMessage)obj);
		else
			_secureChannelEstablished = establishCertificateSecureChannel((CertChallengeMessage)obj);
	}

	public boolean establishUserPassSecureChannel(UserPassPKExchangeMessage pkExchangeMessage) throws ConnectionResetException {
		_remoteEndpointId = pkExchangeMessage.getUsername();
		String password = _serverData.getDatabaseConnection().getUserPasswordHash(_remoteEndpointId);

		//TODO Change to receive password hashed and ciphered from DB
		String passHash = Hash.getStringHash(password);

		if(passHash == null){
			send(new UnexpectedMessage());
			waitChannelEstablishmentMode();
			return _secureChannelEstablished;
		}	

		_remotePublicKey = pkExchangeMessage.getPublicKey(passHash);

		_simSessionKey = SimmEncryption.generateKey();
		UserPassSKExchangeMessage sessionKeyMessage = new UserPassSKExchangeMessage(passHash, _remotePublicKey, _simSessionKey);
		_channelLastMessage = sessionKeyMessage;
		send(sessionKeyMessage);

		return channelEstablishmentChallenge();
	}

	public boolean establishCcSecureChannel(CcPKExchangeMessage pkMessage) throws ConnectionResetException {
		_remoteEndpointId = pkMessage.getUserId();

		//TODO Change to get certificate from DB
		Certificate certificate = CitizenCardInterface.getCertificate();

		_remotePublicKey = pkMessage.getPublicKey(certificate);

		if(_remotePublicKey == null){
			send(new UnexpectedMessage());
			waitChannelEstablishmentMode();
			return _secureChannelEstablished;
		}

		_simSessionKey = SimmEncryption.generateKey();

		CcSKExchangeMessage sessionKeyMessage = new CcSKExchangeMessage(_simSessionKey, _remotePublicKey, _privateKey);
		_channelLastMessage = sessionKeyMessage;
		send(sessionKeyMessage);

		CcAuthNounceMessage authNounce = (CcAuthNounceMessage)receiveCcProtocolMessage(CcProtocolPhases.WaitingAuthNounce);

		try {
			int responseNounce = authNounce.getNounce(_simSessionKey, _privateKey);
			responseNounce++;			
			CcAuthNounceMessage authNounceResp = new CcAuthNounceMessage(responseNounce, _simSessionKey, _remotePublicKey);
			_channelLastMessage = authNounceResp;
			send(authNounceResp);
		} catch (CryptoException e) {
			return false;
		}

		return channelEstablishmentChallenge();
	}

	public boolean establishCertificateSecureChannel(CertChallengeMessage challengeMessage) throws ConnectionResetException{
		if(_vericop){
			_remotePublicKey = _serverData.getKeystoreManager().getVericopPublicKey();
			_remoteEndpointId = "VERICOP";
		}else{
			_remotePublicKey = _serverData.getKeystoreManager().getWebServerPublicKey();
			_remoteEndpointId = "WEBSERVER";
		}

		int clientChallenge = challengeMessage.getClientChallenge(_privateKey);
		clientChallenge++;

		_simSessionKey = SimmEncryption.generateKey();

		CertSKExchangeMessage sessionKeyMessage = new CertSKExchangeMessage(clientChallenge, _simSessionKey, _remotePublicKey);
		_channelLastMessage = sessionKeyMessage;
		send(sessionKeyMessage);

		SecChannelChallengeMessage challengePair = (SecChannelChallengeMessage)receiveChallengePhaseMessage(ChallengePhases.WaitingChallengePair);

		try {
			int secondClientChallenge = challengePair.getChallenge(clientChallenge, _simSessionKey);

			SecChannelChallengeMessage lastChallenge = new SecChannelChallengeMessage(secondClientChallenge, _simSessionKey);
			_channelLastMessage = lastChallenge;
			send(lastChallenge);

			_establishingSecureChannel = false;
		} catch (CryptoException e) {
			return false;
		}
		return true;
	}

	public Object receive() throws ConnectionResetException{
		return super.receive();
	}
	public void send(Object obj) throws ConnectionResetException{
		super.send(obj);
	}

	public boolean isConnected(){
		return super.isConnected();
	}

	public boolean isSecureChannelEstablished(){
		return super.isSecureChannelEstablished();
	}

	public InetSocketAddress getSocketAddress(){
		return super.getSocketAddress();
	}

	public void isVericop(boolean vericop) {
		_vericop = vericop;

	}

	private Object receiveChallengePhaseMessage(ChallengePhases phase) throws ConnectionResetException{
		Object obj = null;

		for(int index = 0; index< 6; index++){
			obj = receive();

			if(obj!=null)
				break;
		}

		if(obj == null || obj.getClass().getSimpleName().equals("UnexpectedMessage")){
			send(_channelLastMessage);
			return receiveChallengePhaseMessage(phase);
		}else if(obj.getClass().getSimpleName().equals("SecChannelChallengeMessage")&& phase == ChallengePhases.WaitingClientChallenge){
			return obj;
		}else if(obj.getClass().getSimpleName().equals("SecChannelChallengeMessage")&& phase == ChallengePhases.WaitingServerChallengeAnswer){
			return obj;
		}else if(obj.getClass().getSimpleName().equals("SecChannelChallengeMessage")&& phase == ChallengePhases.WaitingChallengePair){
			return obj;
		}else{
			send(new UnexpectedMessage());
			return receiveChallengePhaseMessage(phase);
		}		
	}

	private Object receiveCcProtocolMessage(CcProtocolPhases phase) throws ConnectionResetException{
		Object obj = null;

		for(int index = 0; index< 6; index++){
			obj = receive();

			if(obj!=null)
				break;
		}

		if(obj == null || obj.getClass().getSimpleName().equals("UnexpectedMessage")){
			send(_channelLastMessage);
			return receiveCcProtocolMessage(phase);
		}else if(obj.getClass().getSimpleName().equals("CcAuthNounceMessage")&& phase == CcProtocolPhases.WaitingAuthNounce){
			return obj;
		}else{
			send(new UnexpectedMessage());
			return receiveCcProtocolMessage(phase);
		}		
	}

	private boolean channelEstablishmentChallenge() throws ConnectionResetException{		
		try {
			SecChannelChallengeMessage challengeMessage = (SecChannelChallengeMessage)receiveChallengePhaseMessage(ChallengePhases.WaitingClientChallenge);

			int clientChallenge = challengeMessage.getChallenge(_simSessionKey);			
			int serverChallenge = SecureRandomNumber.generateNonce();

			SecChannelChallengeMessage challengeResponse = new SecChannelChallengeMessage(clientChallenge, serverChallenge, _simSessionKey);
			_channelLastMessage = challengeResponse;
			send(challengeResponse);

			SecChannelChallengeMessage serverChallengeResponse = (SecChannelChallengeMessage)receiveChallengePhaseMessage(ChallengePhases.WaitingServerChallengeAnswer);
			int serverChallengeResValue = serverChallengeResponse.getChallenge(_simSessionKey);

			if(serverChallengeResValue == serverChallenge){
				_establishingSecureChannel = false;
				return true;
			}else{
				send(new UnexpectedMessage());
				return false;
			}

		} catch (CryptoException e) {
			return false;
		}
	}
}
